#!/usr/bin/python
#
# Copyright (C) 2008 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


__author__ = 'api.jscudder (Jeff Scudder)'


import atom.http_interface
import atom.url


class Error(Exception):
    pass


class NoRecordingFound(Error):
    pass


class MockRequest(object):
    """Holds parameters of an HTTP request for matching against future requests.
    """
    def __init__(self, operation, url, data=None, headers=None):
        self.operation = operation
        if isinstance(url, (str, unicode)):
            url = atom.url.parse_url(url)
        self.url = url
        self.data = data
        self.headers = headers


class MockResponse(atom.http_interface.HttpResponse):
    """Simulates an httplib.HTTPResponse object."""
    def __init__(self, body=None, status=None, reason=None, headers=None):
        if body and hasattr(body, 'read'):
            self.body = body.read()
        else:
            self.body = body
        if status is not None:
            self.status = int(status)
        else:
            self.status = None
        self.reason = reason
        self._headers = headers or {}

    def read(self):
        return self.body


class MockHttpClient(atom.http_interface.GenericHttpClient):
    def __init__(self, headers=None, recordings=None, real_client=None):
        """An HttpClient which responds to request with stored data.

        The request-response pairs are stored as tuples in a member list named
        recordings.

        The MockHttpClient can be switched from replay mode to record mode by
        setting the real_client member to an instance of an HttpClient which will
        make real HTTP requests and store the server's response in list of
        recordings.

        Args:
          headers: dict containing HTTP headers which should be included in all
              HTTP requests.
          recordings: The initial recordings to be used for responses. This list
              contains tuples in the form: (MockRequest, MockResponse)
          real_client: An HttpClient which will make a real HTTP request. The
              response will be converted into a MockResponse and stored in
              recordings.
        """
        self.recordings = recordings or []
        self.real_client = real_client
        self.headers = headers or {}

    def add_response(self, response, operation, url, data=None, headers=None):
        """Adds a request-response pair to the recordings list.

        After the recording is added, future matching requests will receive the
        response.

        Args:
          response: MockResponse
          operation: str
          url: str
          data: str, Currently the data is ignored when looking for matching
              requests.
          headers: dict of strings: Currently the headers are ignored when
              looking for matching requests.
        """
        request = MockRequest(operation, url, data=data, headers=headers)
        self.recordings.append((request, response))

    def request(self, operation, url, data=None, headers=None):
        """Returns a matching MockResponse from the recordings.

        If the real_client is set, the request will be passed along and the
        server's response will be added to the recordings and also returned.

        If there is no match, a NoRecordingFound error will be raised.
        """
        if self.real_client is None:
            if isinstance(url, (str, unicode)):
                url = atom.url.parse_url(url)
            for recording in self.recordings:
                if recording[0].operation == operation and recording[0].url == url:
                    return recording[1]
            raise NoRecordingFound('No recodings found for %s %s' % (
                operation, url))
        else:
            # There is a real HTTP client, so make the request, and record the
            # response.
            response = self.real_client.request(operation, url, data=data,
                headers=headers)
            # TODO: copy the headers
            stored_response = MockResponse(body=response, status=response.status,
                reason=response.reason)
            self.add_response(stored_response, operation, url, data=data,
                headers=headers)
            return stored_response
